#include "ExampleScene.h"

#include <graphics/utils.h>
#include <graphics/image2d.h>
#include <assert.h>
#include <math.h>
#include <render/renderer.h>


ExampleScene::ExampleScene()
    : Yam3DScene()
    , m_camera(0)
    , m_mesh(0)
    , m_program(0)
    , m_baseMap(0)
    , m_displacementMap(0)
{
    // Create cool vertex and fragment shaders.
    QGLShader *vshader = new QGLShader(QGLShader::Vertex);
    char vShaderStr[] =
        "uniform mat4 matModelView;                                                   \n"
        "uniform mat4 matModelViewProj;                                               \n"
        "uniform mat4 matModelViewInverseTranspose;                                   \n"
        "uniform sampler2D sDisplacementMap;                                          \n"
        "attribute vec4 a_position;                                                   \n"
        "attribute vec2 a_texCoord;                                                   \n"
        "attribute vec3 a_normal;                                                     \n"
        "varying vec3 vNormalES;                                                      \n"
        "varying vec3 vViewVec;                                                       \n"
        "varying vec2 vTexCoord;                                                      \n"
        "void main()                                                                  \n"
        "{                                                                            \n"
        "   vTexCoord    = a_texCoord;                                                \n"
        "   vNormalES   = vec3(matModelViewInverseTranspose * vec4(a_normal, 0.0));   \n"
        "                                                               \n"
        "   vec4 dv =  texture2D( sDisplacementMap, a_texCoord );       \n"
        "   float df = length(dv);                                  \n"
        "                                                               \n"
        "   vec4 newVertexPos = a_position + vec4(-a_normal*df*500,0.0);      \n"
        "                                                               \n"
        "   vViewVec    = -vec3(matModelView * newVertexPos);                           \n"
        "   gl_Position = matModelViewProj * newVertexPos;                              \n"
        "}                                                              \n";
    vshader->compileSourceCode(vShaderStr);

    QGLShader *fshader = new QGLShader(QGLShader::Fragment);
    char fShaderStr[] =
        "uniform vec4  lightDir;                                                                                      \n"
        "varying vec3 vNormalES;                                                                                      \n"
        "varying vec2 vTexCoord;                                                                                      \n"
        "varying vec3 vViewVec;                                                                                       \n"
        "uniform sampler2D sBaseMap;                                                                                  \n"
        "void main()                                                                                                  \n"
        "{                                                                                                            \n"
        "    vec3 normal = normalize(vNormalES);                                                                      \n"
        "    // Soft diffuse                                                                                          \n"
        "    float diffuse = 0.5 + 0.5 * dot(lightDir.xyz, normal);                                                   \n"
        "    // Standard specular                                                                                     \n"
        "    float specular = pow(clamp(dot(reflect(-normalize(vViewVec), normal), lightDir.xyz),0.0, 1.0), 6.0);     \n"
        "                                                                                                             \n"
        "    gl_FragColor = texture2D( sBaseMap, vTexCoord ) * diffuse + vec4(0.9,0.9,0.9,1.0) * (0.5* specular);            \n"
        "}                                                                                                            \n";
    fshader->compileSourceCode(fShaderStr);

    assert( m_program == 0 );
    m_program = new QGLShaderProgram();
    m_program->addShader(vshader);
    m_program->addShader(fshader);
    m_program->link();
    m_program->bind();


    // Create camera for our scene.
    assert( m_camera == 0 );
    m_camera = new graphics::Camera();
    m_camera->setTranslation( QVector3D(0,500,1000.0f) );
    m_camera->setOrientation( QQuaternion::fromAxisAndAngle(1,0,0,-30) );

    m_mesh = graphics::genPlane( 640, 480, 640, 480, 640.0f/1024.0f, 480.0f/1024.0f );
    m_mesh->setOrientation( QQuaternion::fromAxisAndAngle(0,0,1,180) * QQuaternion::fromAxisAndAngle(0,1,0,-90) );
    {
        graphics::Image2D img( 1024, 1024 );
        QVector<unsigned char> data = graphics::readFileByteData(":/assets/rgb_640x480.bin");

        int dataIndex=0;
        for( int y=0; y<480; ++y )
        {
            for( int x=0; x<640; ++x)
            {
                assert( dataIndex >= 0 && (dataIndex+2) < data.size() );
                img.getPixel(x,y)[0] = data[dataIndex++];
                img.getPixel(x,y)[1] = data[dataIndex++];
                img.getPixel(x,y)[2] = data[dataIndex++];
                img.getPixel(x,y)[3] = 0xff;
            }
        }

        m_baseMap = new render::Texture2D(&img,false);
    }

    {
        graphics::Image2D img( 1024, 1024 );
        QVector<unsigned short> data = graphics::readFileUShortData(":/assets/depth_640x480.bin");

        int dataIndex=0;
        for( int y=0; y<480; ++y )
        {
            for( int x=0; x<640; ++x)
            {
                assert( dataIndex >= 0 && dataIndex < data.size() );
                unsigned short curVal = data[dataIndex++];

                if( curVal > 0x0ff0 )
                {
                    curVal = 0x0ff0;
                }

                // Fixed value for pixels, which kinect did not get.
                if( curVal < 1 )
                {
                    curVal = 0x0ff0;
                }

                // Maximum "length" of the color can be 510 (because QVector4D(255,255,255,255).length()==510)
                // and kinect can output (0x0ff0 = 4080), so we must divide curVal with 8 )
                curVal >>= 3;

                QVector4D temp = curVal*QVector4D(1,1,1,1).normalized();

                assert(temp.x() >= 0.0f && temp.x() <= 255.0f );
                assert(temp.y() >= 0.0f && temp.y() <= 255.0f );
                assert(temp.z() >= 0.0f && temp.z() <= 255.0f );
                assert(temp.w() >= 0.0f && temp.w() <= 255.0f );

                img.getPixel(x,y)[0] = (unsigned char)temp.x();
                img.getPixel(x,y)[1] = (unsigned char)temp.y();
                img.getPixel(x,y)[2] = (unsigned char)temp.z();
                img.getPixel(x,y)[3] = (unsigned char)temp.w();
            }
        }

        m_displacementMap = new render::Texture2D(&img,false);
    }

    // Configure pass
    m_pass.setCamera(m_camera);
    m_pass.setShaderProgram(m_program);

    m_pass.setTexture( 0, "sBaseMap", m_baseMap );
    m_pass.getSamplerState(0)->setFiltering( graphics::SamplerState::NEAREST, graphics::SamplerState::NEAREST );
    m_pass.setTexture( 1, "sDisplacementMap", m_displacementMap );
    m_pass.getSamplerState(1)->setFiltering( graphics::SamplerState::NEAREST, graphics::SamplerState::NEAREST );

    graphics::ShaderUniforms* uniforms = new graphics::ShaderUniforms();
    m_pass.setShaderUniforms(uniforms);
}

ExampleScene::~ExampleScene()
{
}

void ExampleScene::resize(int width, int height)
{
    m_camera->setPerspective(60.0f, float(width)/float(height), 0.5f, 2000.0f);
    glViewport( 0,0, width, height);
}

void ExampleScene::update(float deltaTime)
{
    m_mesh->setOrientation( m_mesh->getOrientation()* QQuaternion::fromAxisAndAngle(0,1,0,20*deltaTime) );

    // Light dir is in world space, convert it to camera space
    QVector4D lightDir =  m_camera->getView().inverted() * QVector4D(0.0f,1.0f,1.0f,0.0f);
    m_pass.getShaderUniforms()->setUniformValue("lightDir", lightDir.normalized() );
}

void ExampleScene::render() const
{
    render::Renderer renderer;

    renderer.applyPass(&m_pass);

    renderer.render(m_mesh);
}


